/*
 * LibrePCB - Professional EDA for everyone!
 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
 * https://librepcb.org/
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*******************************************************************************
 *  Includes
 ******************************************************************************/
#include "library.h"

#include "cat/componentcategory.h"
#include "cat/packagecategory.h"
#include "cmp/component.h"
#include "dev/device.h"
#include "pkg/package.h"
#include "sym/symbol.h"

#include <librepcb/common/fileio/fileutils.h>
#include <librepcb/common/fileio/sexpression.h>
#include <librepcb/common/fileio/smartsexprfile.h>
#include <librepcb/common/toolbox.h>

#include <QtCore>
#include <QtWidgets>

/*******************************************************************************
 *  Namespace
 ******************************************************************************/
namespace librepcb {
namespace library {

/*******************************************************************************
 *  Constructors / Destructor
 ******************************************************************************/

Library::Library(const Uuid& uuid, const Version& version,
                 const QString& author, const ElementName& name_en_US,
                 const QString& description_en_US,
                 const QString& keywords_en_US)
  : LibraryBaseElement(false, getShortElementName(), getLongElementName(), uuid,
                       version, author, name_en_US, description_en_US,
                       keywords_en_US) {
}

Library::Library(const FilePath& libDir, bool readOnly)
  : LibraryBaseElement(libDir, false, "lib", "library", readOnly) {
  // check directory suffix
  if (libDir.getSuffix() != "lplib") {
    throw RuntimeError(__FILE__, __LINE__,
                       QString(tr("The library directory does not have the "
                                  "suffix '.lplib':\n\n%1"))
                           .arg(libDir.toNative()));
  }

  // read properties
  try {
    mUrl = mLoadingFileDocument.getValueByPath<QUrl>("url");
  } catch (const Exception& e) {
    qWarning() << e.getMsg();
  }

  // read dependency UUIDs
  foreach (const SExpression& node,
           mLoadingFileDocument.getChildren("dependency")) {
    mDependencies.insert(node.getValueOfFirstChild<Uuid>());
  }

  // load image if available
  if (getIconFilePath().isExistingFile()) {
    mIcon = FileUtils::readFile(getIconFilePath());  // can throw
  }

  cleanupAfterLoadingElementFromFile();
}

Library::~Library() noexcept {
}

/*******************************************************************************
 *  Getters
 ******************************************************************************/

template <typename ElementType>
FilePath Library::getElementsDirectory() const noexcept {
  return mDirectory.getPathTo(ElementType::getShortElementName());
}

// explicit template instantiations
template FilePath Library::getElementsDirectory<ComponentCategory>() const
    noexcept;
template FilePath Library::getElementsDirectory<PackageCategory>() const
    noexcept;
template FilePath Library::getElementsDirectory<Symbol>() const noexcept;
template FilePath Library::getElementsDirectory<Package>() const noexcept;
template FilePath Library::getElementsDirectory<Component>() const noexcept;
template FilePath Library::getElementsDirectory<Device>() const noexcept;

FilePath Library::getIconFilePath() const noexcept {
  return mDirectory.getPathTo("library.png");
}

QPixmap Library::getIconAsPixmap() const noexcept {
  QPixmap p;
  p.loadFromData(mIcon, "png");
  return p;
}

/*******************************************************************************
 *  General Methods
 ******************************************************************************/

void Library::save() {
  LibraryBaseElement::save();  // can throw

  // Save icon.
  if (mIcon.isEmpty() && getIconFilePath().isExistingFile()) {
    FileUtils::removeFile(getIconFilePath());  // can throw
  } else if (!mIcon.isEmpty()) {
    FileUtils::writeFile(getIconFilePath(), mIcon);  // can throw
  }
}

template <typename ElementType>
QList<FilePath> Library::searchForElements() const noexcept {
  QList<FilePath> list;
  FilePath        subDirFilePath = getElementsDirectory<ElementType>();
  QDir            subDir(subDirFilePath.toStr());
  foreach (const QString& dirname,
           subDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
    FilePath elementFilePath = subDirFilePath.getPathTo(dirname);
    if (isValidElementDirectory<ElementType>(elementFilePath)) {
      list.append(elementFilePath);
    } else if (elementFilePath.isEmptyDir()) {
      qInfo() << "Empty library element directory will be removed:"
              << elementFilePath.toNative();
      // TODO: This is actually a race condition, because the directory may be
      // created just a moment ago in the main thread, and is thus still empty.
      // Even if not very critical, this should be made thread-safe some time ;)
      QDir(elementFilePath.toStr()).removeRecursively();
    } else {
      qWarning() << "Directory is not a valid library element:"
                 << elementFilePath.toNative();
    }
  }
  return list;
}

// explicit template instantiations
template QList<FilePath> Library::searchForElements<ComponentCategory>() const
    noexcept;
template QList<FilePath> Library::searchForElements<PackageCategory>() const
    noexcept;
template QList<FilePath> Library::searchForElements<Symbol>() const noexcept;
template QList<FilePath> Library::searchForElements<Package>() const noexcept;
template QList<FilePath> Library::searchForElements<Component>() const noexcept;
template QList<FilePath> Library::searchForElements<Device>() const noexcept;

/*******************************************************************************
 *  Private Methods
 ******************************************************************************/

void Library::copyTo(const FilePath& destination, bool removeSource) {
  // check directory suffix
  if (destination.getSuffix() != "lplib") {
    qDebug() << destination.toStr();
    throw RuntimeError(
        __FILE__, __LINE__,
        QString(tr("A library directory name must have the suffix '.lplib'.")));
  }

  // copy the element
  LibraryBaseElement::copyTo(destination, removeSource);
}

void Library::serialize(SExpression& root) const {
  LibraryBaseElement::serialize(root);
  root.appendChild("url", mUrl, true);
  foreach (const Uuid& uuid, Toolbox::sortedQSet(mDependencies)) {
    root.appendChild("dependency", uuid, true);
  }
}

/*******************************************************************************
 *  End of File
 ******************************************************************************/

}  // namespace library
}  // namespace librepcb
